/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "MonoEngine.h"

#include "ScriptComponent.h"
#include "GameObjectManager.h"
#include "MonoInternalCall.h"

#include "GameEngine.h"

namespace PQEngine
{
	MonoEngine::MonoEngine()
	{

	}

	MonoEngine::~MonoEngine()
	{
		mono_environment_exitcode_get ();
		mono_jit_cleanup(_domain);
	}

	MonoEngine& MonoEngine::getSingleton()
	{
		static MonoEngine inst;
		return inst;
	}

	//after all game object is loaded
	//this init is after all other system is init
	//for this depend on other class info
	//last system to invoke
	void MonoEngine::init(GameObject* root)
	{
		mono_set_dirs("lib","etc"); 
		mono_config_parse (NULL);
		mono_add_internal_call ("PGSK3D.InternalCall::OnCreateGameObject", MonoInternalCall::OnCreateGameObject);

		_domain = mono_jit_init ("PGSK3D.dll");
		_monoLibAssembly= mono_domain_assembly_open (_domain, "PGSK3D.dll");

		_monoLibImage =mono_assembly_get_image (_monoLibAssembly);

		//initMonoClasses();
		initMonoExecutor();

		//in mono space game world
		//initGameObjects();//
		//initRootGameObject(root);
	}

	void MonoEngine::initMonoExecutor()
	{
	}

	void MonoEngine::initRootGameObject(GameObject* root)
	{
	
	}

	void MonoEngine::initMonoClasses()
	{
		_monoEngineClass= mono_class_from_name (_monoLibImage, "PGSK3D", "MonoEngine");

		if (!_monoEngineClass) {
			return;
		}
		
		gpointer iter = NULL;
		MonoMethod* m=NULL;

		while ((m = mono_class_get_methods (_monoEngineClass, &iter))) {
			if (strcmp (mono_method_get_name (m), "createGameObjectFromHost") == 0) {
				_createGameObject=m;
			}
			if (strcmp (mono_method_get_name (m), "createComponentFromHost") == 0) {
				_createComponent=m;
			}
			if (strcmp (mono_method_get_name (m), "createScriptComponentFromHost") == 0) {
				_createScript=m;
			}
			if (strcmp (mono_method_get_name (m), "attachComponentFromHost") == 0) {
				_attachComponent=m;
			}
			if (strcmp (mono_method_get_name (m), "addChildGameObjectFromHost") == 0) {
				_addChildObject=m;
			}
			if (strcmp (mono_method_get_name (m), "updateFromHost") == 0) {
				_updateMonoEngine=m;
			}
		}
	}

	void MonoEngine::initGameObjects()
	{
		//gpointer args[2];
		//args[0]=&id;
		//MonoArray* bytes = mono_array_new (_domain, mono_get_byte_class (), 256);
		//mono_array_memcpy_refs
		//memcpy((char*)(bytes->vector),
		//mono_array_set(bytes,mono_get_byte_class (),0,
		 //( ((char*)(array)->vector) + (size) * (index) )

		//mono_runtime_invoke (ctor, obj, args, NULL);
		
		GameObject* rootObject=SEngine.getSceneObject()->getRootGameObject();
		addGameObject(rootObject);
	}

	void MonoEngine::addGameObject(GameObject* object)
	{
		createMonoGameObject(object);
		int cnt=object->getComponentCount();
		for(int i=0;i<cnt;i++){
			addComponent(object,object->getComponent(i));
		}

		cnt=object->getChildrenCount();
		for(int i=0;i<cnt;i++){
			addGameObject(object->getChild(i));
			addChildGameObject(object->getId(),object->getChild(i)->getId());
		}
	}

	void MonoEngine::addChildGameObject(int parentId,int childId)
	{
		MonoObject *exception;
		gpointer args[2];
		args[0]=&parentId;
		args[1]=&childId;
		if(_addChildObject){
			mono_runtime_invoke(_addChildObject, NULL, args, &exception);
		}
	}

	void MonoEngine::createMonoGameObject(GameObject* object)
	{
		int id=object->getId();
		gpointer args[2];
		args[0]=&id;
		
		MonoObject *exception;
		bool isRoot=object->isRoot();
		args[1]=&isRoot;
		mono_runtime_invoke(_createGameObject, NULL, args, &exception);
	}

	void MonoEngine::addComponent(GameObject* object,Component* component)
	{
		if(object==NULL||component==NULL){
			return;
		}

		createMonoComponent(component);
		addMonoComponent(object->getId(),component->getId());
	}

	void MonoEngine::addMonoComponent(int objectId,int componentId)
	{
		MonoObject *exception;
		gpointer args[2];
		args[0]=&objectId;
		args[1]=&componentId;
		if(_attachComponent){
			mono_runtime_invoke(_attachComponent, NULL, args, &exception);
		}
	}

	void MonoEngine::createMonoComponent(Component* component)
	{
		int type=component->getObjectType();
		int id=component->getId();
		MonoObject *exception;
		gpointer args[2];
		args[0]=&id;
		args[1]=&type;

		if(type==OBJECT_TYPE_COMPONENT_SCRIPT){
			ScriptComponent* script=(ScriptComponent*)component;
			std::string classFullName="PGSK3D."+script->getScriptName();
			args[0]=&id;
			args[1]=mono_string_new(_domain,classFullName.c_str());
			if(_createScript){
				mono_runtime_invoke(_createScript, NULL, args, &exception);
			}
		}else{
			args[0]=&id;
			args[1]=&type;
			if(_createComponent){
				mono_runtime_invoke(_createComponent, NULL, args, &exception);
			}
		}
	}

	void MonoEngine::update(float deltaT)
	{
		MonoObject *exception;
		gpointer args[1];
		args[0]=&deltaT;
		if(_updateMonoEngine){
			mono_runtime_invoke(_updateMonoEngine, NULL, args, &exception);
		}
	}

	bool MonoEngine::createMesh(std::string name,int x,int y)
	{
		return false;
	}


}
